package org.tech4j.springboot.starter.distributedlock.api.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.tech4j.springboot.starter.distributedlock.api.ILock;
import org.tech4j.springboot.starter.distributedlock.api.constant.DistributeLockConstant;
import org.tech4j.springboot.starter.distributedlock.api.exception.DistributeLockException;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @program: ppc-common
 * @description:
 * @author: YuKai Fan
 * @create: 2024/11/28 15:23
 **/
@Aspect
public class DistributeLockAspect {
    private static final Logger log = LoggerFactory.getLogger(DistributeLockAspect.class);
    private final ILock lock;

    public DistributeLockAspect(ILock lock) {
        this.lock = lock;
    }

    @Around("@annotation(org.tech4j.springboot.starter.distributedlock.api.aop.DistributeLock)")
    public Object process(ProceedingJoinPoint point) throws Exception{
        Object response = null;
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        DistributeLock distributeLock = method.getAnnotation(DistributeLock.class);

        String key = distributeLock.key();
        if (DistributeLockConstant.NONE_KEY.equals(key)) {
            if (DistributeLockConstant.NONE_KEY.equals(distributeLock.keyExpression())) {
                throw new DistributeLockException("no lock key found...");
            }
            SpelExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(distributeLock.keyExpression());
            EvaluationContext context = new StandardEvaluationContext();
            // 获取参数值
            Object[] args = point.getArgs();
            StandardReflectionParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);
            // 将参数绑定到context中
            if (parameterNames != null) {
                for (int i = 0; i < parameterNames.length; i++) {
                    context.setVariable(parameterNames[i], args[i]);
                }
            }

            // 解析表达式获取key
            key = String.valueOf(expression.getValue(context));
        }

        String scene = distributeLock.scene();
        String lockKey = scene + "#" + key;
        int expireTime = distributeLock.expireTime();
        int waitTime = distributeLock.waitTime();
        boolean lockResult;
        if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
            if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
                log.info(String.format("lock for key : %s", lockKey));
                lock.lock(lockKey);
            } else {
                log.info(String.format("lock for key : %s, expire : %s", lockKey, expireTime));
                lock.lock(lockKey, expireTime, TimeUnit.MILLISECONDS);
            }
            lockResult = true;
        } else {
            if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
                log.info(String.format("try lock for key : %s, wait : %s", lockKey, waitTime));
                lockResult = lock.tryLock(lockKey, waitTime, TimeUnit.MILLISECONDS);
            } else {
                log.info(String.format("try lock for key : %s, expire : %s, wait : %s", lockKey, expireTime, waitTime));
                lockResult = lock.tryLock(lockKey, waitTime, expireTime, TimeUnit.MILLISECONDS);
            }
        }
        if (!lockResult) {
            log.warn(String.format("lock failed for key : %s , expire : %s", lockKey, expireTime));
            throw new DistributeLockException("acquire lock failed... key : " + lockKey);
        }
        try {
            log.info(String.format("lock success for key : %s , expire : %s", lockKey, expireTime));
            response = point.proceed();
        } catch (Throwable e) {
            throw new Exception(e);
        } finally {
            lock.unlock(lockKey);
            log.info(String.format("unlock for key : %s , expire : %s", lockKey, expireTime));
        }
        return response;
    }

}
